#!/usr/bin/python

"""
Contextual SSH
@author cwilcox
@date   2008.02.26

Set the color of the calling terminal based on the hostname

RGB to HEX conversions thanks to: http://code.activestate.com/recipes/266466/
RGB to HSB conversions thanks to: http://delphi.about.com/od/adptips2006/qt/RgbToHsb.htm
"""

import sys
import os
import subprocess

real_ssh = 'ssh' #'/usr/bin/ssh'

def str2color(color):
	"""
	Hash the given string to a color representation
	"""
	import md5
	hash = md5.md5(color)
	return Color('#%s' % hash.hexdigest()[:6])


class SSH(object):
	NO_ARGS  = '1246AaCfgKkMNnqsTtVvXxY'
	REQ_ARGS = 'bcDeFiLlmOopRSw'
	OPT_ARGS = '' # No Support Implemented yet

	class FormatError(Exception):
		pass

	def gethostname(self,args):
		import getopt
		


class DarwinSSH(SSH):
	pass


class Terminal(object):
	import pickle

	filename = 'bgcolor.cssh'
	savedir = os.path.join(os.path.expanduser('~'),'.cssh')

	def savebgcolor(self,color):
		try: 
			os.mkdir(self.savedir) # Make our ~/.cssh dir
		except:
			pass # if mkdir fails the dir is probably already there

		f = open(os.path.join(self.savedir,self.filename),'w')
		
		try:
			self.pickle.dump(color,f)
		finally:
			f.close()

	def loadbgcolor(self,color):
		f = open(self.filename,'r')
		try:
			self.pickle.load(f)
		finally:
			f.close()


class DarwinTerminal(Terminal):
	import subprocess

	tab_selector = """tell application "Terminal" to tell its front window to tell selected tab to """
	window_selector = """tell application "Terminal" to tell its front window to """
	osabin = 'osascript'

	def gettitle(self,title):
		subp = self.subprocess
		osascript = self.window_selector + """get custom title""" 
		osa_proc = subp.Popen(self.osabin,stdin=subp.PIPE,stderr=subp.PIPE,stdout=subp.PIPE)
		(stdout,stderr) = osa_proc.communicate(osascript)
		return Color("{%s}" % stdout.strip())
		

	def settitle(self,title):
		subp = self.subprocess
		osascript = self.window_selector + """set custom title to \"%s\"""" % (title)
		osa_proc = subp.Popen(self.osabin,stdin=subp.PIPE,stderr=subp.PIPE,stdout=subp.PIPE)
		(stdout,stderr) = osa_proc.communicate(osascript)
		# TODO: Make settitle actually work
		#print "stdout: %s\n stderr: %s" % (stdout, stderr)

	def getbgcolor(self):
		subp = self.subprocess
		osascript = self.tab_selector + """get background color""" 
		osa_proc = subp.Popen(self.osabin,stdin=subp.PIPE,stderr=subp.PIPE,stdout=subp.PIPE)
		(stdout,stderr) = osa_proc.communicate(osascript)
		return Color("{%s}" % stdout.strip())

	def setbgcolor(self,color):
		# Save the old terminal color to a file
		oldbg = self.getbgcolor()
		Terminal.savebgcolor(self,oldbg)
		# Set a good foreground color
		self.setfgcolor(color.standout())

		subp = self.subprocess
		osascript = self.tab_selector + """set background color to {%s,%s,%s}""" % (color.apple)
		osa_proc = subp.Popen(self.osabin,stdin=subp.PIPE,stderr=subp.PIPE,stdout=subp.PIPE)
		(stdout,stderr) = osa_proc.communicate(osascript)

	def setfgcolor(self,color):
		subp = self.subprocess
		osascript = self.tab_selector + """set normal text color to {%s,%s,%s}""" % (color.apple)
		osa_proc = subp.Popen(self.osabin,stdin=subp.PIPE,stderr=subp.PIPE,stdout=subp.PIPE)
		(stdout,stderr) = osa_proc.communicate(osascript)
		

def getTerminal():
	import platform
	platform = platform.system()	
	term = None
	if platform == 'Darwin':
		term = DarwinTerminal()
	elif platform == 'Linux':
		raise Exception,'Unsupported OS found, Linux Support coming soon.'
	elif platform == 'Windows':
		raise Exception,'Unsupported OS found'
	else:
		raise Exception,'Unsupported OS found'

	return term

def setbgcolor(color):
	term = getTerminal()
	exit_status = term.setbgcolor(color)

def settitle(title):
	term = getTerminal()
	exit_status = term.settitle(title)

def AppleColorToRGB(apple_tuple):
	""" Convert an apple color tuple (xxxxx,yyyyy,zzzzz) tuple into RGB"""
	apple_tuple = map(lambda x: int(x),apple_tuple)
	return tuple(map(lambda x: x*256/0xFFFF,apple_tuple))

def RGBtoAppleColor(rgb_tuple):
	""" Convert an (R,G,B) tuple into apple script colors (xxxxx,yyyyy,zzzzz)"""
	return tuple(map(lambda x: x*0xFFFF/256,rgb_tuple)) #[0xFFFF])

def RGBToHTMLColor(rgb_tuple):
	""" convert an (R, G, B) tuple to #RRGGBB """
	hexcolor = '#%02x%02x%02x' % rgb_tuple
	# that's it! '%02x' means zero-padded, 2-digit hex values
	return hexcolor

def HTMLColorToRGB(colorstring):
	""" convert #RRGGBB to an (R, G, B) tuple """
	colorstring = colorstring.strip()
	if colorstring[0] == '#': colorstring = colorstring[1:]
	if len(colorstring) != 6:
       		raise ValueError, "input #%s is not in #RRGGBB format" % colorstring
 	r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
	r, g, b = [int(n, 16) for n in (r, g, b)]
	return (r, g, b)

def RGBtoHSB(rgb_tuple):
	""" convert RGB into Hue Saturation Brightness 
	I think this might not be a very accurate conversion"""
	rgb = map(lambda x: float(x),rgb_tuple)
	h = 0.0
	minrgb = min(min(rgb[0:1]),rgb[2])
	maxrgb = max(max(rgb[0:1]),rgb[2])
	delta  = maxrgb - minrgb
	b = maxrgb
	if maxrgb != 0.0:
		s = 255*delta/maxrgb
	else:
		s = 0.0
	if s != 0.0:
		if rgb[0] == maxrgb: h = (rgb[1] - rgb[2]) /delta 
		elif rgb[1] == maxrgb: h = 2+(rgb[2] - rgb[0]) /delta 
		elif rgb[2] == maxrgb: h = 4+(rgb[0] - rgb[1]) /delta 
	else:
		h = -1.0
	h *= 60.0
	if h < 0.0: h += 360.0
	return (h,s*100/255,b*100/255)	

	
class Color(object):

	apple = None

	def __str__(self):
		return str(self.rgb)

	def __init__(self,color): # Let's store everything as apple format, cuz has highest color resolution
		typ = type(color)
		if typ == str and len(color) == 7 and color.startswith('#'): # Look for format #XXXXXX
			self.apple = RGBtoAppleColor(HTMLColorToRGB(color))
		elif typ == str and color.startswith('{') and len(color.split(',')) == 3: # Look for format {34,34,34} (as a string)
			self.apple = tuple(map(lambda x: x.strip(),color.strip('{}').split(',')))
		elif typ == tuple and len(color) == 3 and len(filter(lambda x: x>255,color)) == 0: # look for tuple whose entries are all less tan 255
			self.apple = RGBtoAppleColor(color)
		else:
			raise ValueError,"Bad color input: '%s'. Please input (R,G,B) tuple, Apple Color string '{xx,yy,zz}', or hex  color string #XXXXXX" % color

	def __rgb(self):
		return AppleColorToRGB(self.apple)
	rgb = property(__rgb)
	
	def __hex(self):
		return RGBtoHTMLColor(self.rgb)
	hex = property(__hex)

	def __hsb(self):
		return RGBtoHSB(self.rgb)
	hsb = property(__hsb)

	def standout(self):
		"""Calculate a color that standsout against this color"""
		# TODO: Implement actual complemetary colors instead of just black and white
		if self.hsb[2] > 45: # If brightness is less than 45, then lets use white
			return Color((0,0,0))
		else:
			return Color((255,255,255))


def extracthostname(args):
	if len(args) < 1:
		raise ValueError,"No hostname provided"

	i = 0
	while args[i].startswith('-'):
		i+=2
	
	host = args[i]
	hostname = host.split('@')
	if len(hostname) > 1:
		hostname = hostname[1]
	else:
		hostname = hostname[0]
	return hostname


def host2ip(hostname):
	import socket
	try:
		return socket.getaddrinfo(hostname,22)[0][4][0] #Get the IP address of the given hostname
	except socket.gaierror:
		fail_graceful('Unable to lookup host: %s' % hostname)
	except Exception, e:
		fail_graceful(e)

def dossh(args):
	import subprocess
	subprocess.Popen('%s %s' % (real_ssh,' '.join(args)))


def main(args=None):
	# Should we check for an SSH binary first?
	#if os.access(real_ssh,os.X_OK):
	#	fail_catastroph("Can't find ssh bin! (Looked in %s)" % (real_ssh))

	try:
		if not args:
			args = sys.argv[1:]
		hostname = extracthostname(args)
		ip = host2ip(hostname)
		color = str2color(ip)
		setbgcolor(color)
		print  "%s %s is colored %s" % (ip,'(%s)' % hostname, tuple(map(lambda x: int(x),color.hsb)))
		settitle("SSH (%s)" % (hostname))
	except KeyboardInterrupt:
		sys.exit(1)
	# TODO: Change colors back again with exit
	# Actually lets not even start ssh from python
	# TODO: Connect stdins/out/errs to ssh!
	#dossh(args) # We want to run ssh even if we fuck up

def fail_graceful(warn):
	print '[%s] Warning: %s' % (__file__,warn)
	sys.exit(0)	

def fail_catastroph(error):
	print '[%s] Error: %s. Did not attempt SSH connectiontion.' % (__file__,error)
	sys.exit(1)

if __name__ == '__main__':
	main()

